package adfc2.render3d.mesh;

import adfc2.render3d.IRenderable;
import adfc2.render3d.TextureReference;

import javax.media.opengl.GL;

import static java.lang.Math.PI;

/**
 * Примитив --- эллипсоид
 */
public class Ellipsoid extends Mesh {

    /**
     * @param center Координаты центра
     */
    public static Ellipsoid sphere(double[] color, double[] center, double radius, TextureReference texture) {
        return new Ellipsoid(color, center, new double[]{radius, radius, radius}, texture);
    }

    /**
     * @param center Координаты центра
     * @param radius Радиусы по x,y,z
     */
    public Ellipsoid(double[] color, double[] center, double[] radius, TextureReference texture) {
        super(primitivesFor(color, center, radius, texture));
    }

    /**
     * Число "долек"
     */
    public static int N_SLICES = 16;
    /**
     * Число "слоёв"
     */
    public static int N_STACKS = 8;
    /**
     * Значения синуса и косинуса от 0 до 360 включительно
     */
    public static double[] SINXY_VALUES = new double[N_SLICES + 1];
    public static double[] COSXY_VALUES = new double[N_SLICES + 1];
    /**
     * Значения синуса и косинуса от 180 до -180 включительно
     */
    public static double[] SINZ_VALUES = new double[N_STACKS + 1];
    public static double[] COSZ_VALUES = new double[N_STACKS + 1];

    static {
        for (int i = 0; i < N_SLICES + 1; i++) {
            SINXY_VALUES[i] = Math.sin(2 * PI * i / N_SLICES);
            COSXY_VALUES[i] = Math.cos(2 * PI * i / N_SLICES);
        }
        for (int i = 0; i < N_STACKS + 1; i++) {
            SINZ_VALUES[i] = Math.sin(PI / 2 - (PI * i) / N_STACKS);
            COSZ_VALUES[i] = Math.cos(PI / 2 - (PI * i) / N_STACKS);
        }
    }

    private static IRenderable[] primitivesFor(double[] color, double[] center, double[] radius, TextureReference texture) {
        double cx = center[0];
        double cy = center[1];
        double cz = center[2];
        double rx = radius[0];
        double ry = radius[1];
        double rz = radius[2];

        IRenderable[] primitives = new IRenderable[3];

        // Верхняя и нижняя "шапки". Triangle Fan: 1 вершина в центре и (1+NSLICES) вокруг
        double[] top_vertices = new double[(N_SLICES + 2) * 3];
        double[] top_texcoord = (texture == null) ? null : new double[(N_SLICES + 2) * 2];
        double[] top_normals = new double[(N_SLICES + 2) * 3];
        double[] bottom_vertices = new double[top_vertices.length];
        double[] bottom_texcoord = (texture == null) ? null : new double[top_texcoord.length];
        double[] bottom_normals = new double[top_normals.length];
        // центры шапок
        top_vertices[0] = cx;
        top_vertices[1] = cy;
        top_vertices[2] = cz + rz;
        bottom_vertices[0] = cx;
        bottom_vertices[1] = cy;
        bottom_vertices[2] = cz - rz;
        if (texture != null) {
            top_texcoord[0] = 0.5;
            top_texcoord[1] = 0;
            bottom_texcoord[0] = 0.5;
            bottom_texcoord[1] = 1;
        }
        top_normals[0] = 0;
        top_normals[1] = 0;
        top_normals[2] = 1;
        bottom_normals[0] = 0;
        bottom_normals[1] = 0;
        bottom_normals[2] = -1;
        for (int i = 0; i <= N_SLICES; i++) {

            int ii = i * 3 + 3;
            // тут в принципе всё просто. формула перевода из полярных 3д координат. при этом вертикальный угол строго задан
            top_vertices[ii + 0] = cx + COSXY_VALUES[i] * COSZ_VALUES[1] * rx;
            top_vertices[ii + 1] = cy + SINXY_VALUES[i] * COSZ_VALUES[1] * ry;
            top_vertices[ii + 2] = cz + SINZ_VALUES[1] * rz;
            top_normals[ii + 0] = COSXY_VALUES[i] * COSZ_VALUES[1];
            top_normals[ii + 1] = SINXY_VALUES[i] * COSZ_VALUES[1];
            top_normals[ii + 2] = SINZ_VALUES[1];
            if (texture != null) {
                // на шапки отображаются "треугольники" текстуры. углы текстуры потеряются
                top_texcoord[i * 2 + 2] = 1.0 * i / N_SLICES;
                top_texcoord[i * 2 + 3] = 1.0 / N_STACKS;
                bottom_texcoord[i * 2 + 2] = 1.0 * (N_SLICES - i) / N_SLICES;
                bottom_texcoord[i * 2 + 3] = 1.0 * (N_STACKS - 1.0) / N_STACKS;
            }
        }
        // окружность нижней шапки - проход в обратном порядке (culling)
        for (int i = 0; i <= N_SLICES; i++) {
            int ii = i * 3 + 3;
            int bi = (N_SLICES + 1 - i) * 3;
            bottom_vertices[ii + 0] = top_vertices[bi + 0];
            bottom_vertices[ii + 1] = top_vertices[bi + 1];
            bottom_vertices[ii + 2] = top_vertices[bi + 2] - SINZ_VALUES[1] * rz * 2;
            bottom_normals[ii + 0] = top_normals[bi + 0];
            bottom_normals[ii + 1] = top_normals[bi + 1];
            bottom_normals[ii + 2] = -top_normals[bi + 2];
        }

        // охват сферы. состоит из N_STACKS-2 полос квадов, по N_SLICES квадов в каждом
        int body_npoints = (N_STACKS - 2) * (N_SLICES + 1) * 2;
        double[] body_vertices = new double[body_npoints * 3];
        double[] body_texcoord = (texture == null) ? null : new double[body_npoints * 2];
        double[] body_normals = new double[body_npoints * 3];
        for (int i = 1; i < N_STACKS - 1; i++) {
            int vsti = (i - 1) * (N_SLICES + 1) * 3 * 2;
            int tsti = (i - 1) * (N_SLICES + 1) * 2 * 2;
            for (int j = 0; j <= N_SLICES; j++) {
                int vii = vsti + j * 3 * 2;
                // верхняя вершина, нижняя вершина
                body_vertices[vii + 0] = cx + COSXY_VALUES[j] * COSZ_VALUES[i] * rx;
                body_vertices[vii + 1] = cy + SINXY_VALUES[j] * COSZ_VALUES[i] * ry;
                body_vertices[vii + 2] = cz + SINZ_VALUES[i] * rz;
                body_vertices[vii + 3] = cx + COSXY_VALUES[j] * COSZ_VALUES[i + 1] * rx;
                body_vertices[vii + 4] = cy + SINXY_VALUES[j] * COSZ_VALUES[i + 1] * ry;
                body_vertices[vii + 5] = cz + SINZ_VALUES[i + 1] * rz;
                body_normals[vii + 0] = COSXY_VALUES[j] * COSZ_VALUES[i];
                body_normals[vii + 1] = SINXY_VALUES[j] * COSZ_VALUES[i];
                body_normals[vii + 2] = SINZ_VALUES[i];
                body_normals[vii + 3] = COSXY_VALUES[j] * COSZ_VALUES[i + 1];
                body_normals[vii + 4] = SINXY_VALUES[j] * COSZ_VALUES[i + 1];
                body_normals[vii + 5] = SINZ_VALUES[i + 1];
                if (texture != null) {
                    int tii = tsti + j * 2 * 2;
                    body_texcoord[tii + 0] = 1.0 * j / N_SLICES;
                    body_texcoord[tii + 1] = 1.0 * i / N_STACKS;
                    body_texcoord[tii + 2] = 1.0 * j / N_SLICES;
                    body_texcoord[tii + 3] = 1.0 * (i + 1) / N_STACKS;
                }
            }
        }

        primitives[0] = new Primitive(GL.GL_TRIANGLE_FAN, color, top_vertices, top_texcoord, top_normals, texture);
        primitives[1] = new Primitive(GL.GL_TRIANGLE_FAN, color, bottom_vertices, bottom_texcoord, bottom_normals, texture);
        primitives[2] = new Primitive(GL.GL_QUAD_STRIP, color, body_vertices, body_texcoord, body_normals, texture);
        return primitives;
    }
}
